﻿/*  GRBL-Plotter. Another GCode sender for GRBL.
    This file is part of the GRBL-Plotter application.
   
    Copyright (C) 2018 Sven Hasemann contact: svenhb@web.de

    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/
/*
 * 2019-07-08 add xmlMarker.figureStart tag
*/

using System;
using System.Collections.Generic;
using System.Drawing;
using System.Text;

namespace GRBL_Plotter
{   public partial class GCodeFromImage
    {
        private static Dictionary<int, StringBuilder> gcodeByToolNr = new Dictionary<int, StringBuilder>();
        private static int gcodeStringIndex = 0;
        private static StringBuilder finalString = new StringBuilder();
        private static StringBuilder tmpString = new StringBuilder();

        float coordX;   //X
        float coordY;   //Y
        float lastX;    //Last x/y  coords for compare
        float lastY;

        public void generateGCodePreset(float startX, float startY)
        {   lblStatus.Text = "Generating GCode... ";    //Generate picture Gcode
            gcodeByToolNr.Clear();
            finalString.Clear();
            colorMap.Clear();
            colorStart = -2; colorEnd = -2; lastTool = -2; lastLine = -2;   // setColorMap startup
            gcode.setup();
            gcode.jobStart(finalString);
            gcode.MoveToRapid(finalString, startX, startY);
        }

        public void generateGCodeHorizontal()
        {
            Logger.Debug("generateGCodeHorizontal");
            if (resultImage == null) return;          //if no image, do nothing

            float resol = (float)resoDesired;// (float)nUDReso.Value;
            int pixelCount = resultImage.Width * resultImage.Height;
            int pixelProcessed = 0;
            int percentDone = 0;
            generateGCodePreset(0, resol * (resultImage.Height - 1)); // 1st position

            //////////////////////////////////////////////
            // Generate Gcode lines by Horozontal scanning
            //////////////////////////////////////////////
            #region go through all pixels and setColorMap
            int positionY = resultImage.Height - 1;     //top tile
                int positionX = 0;                          //Left pixel
                while (positionY >= 0)
                {   //Y coordinate
                    coordY = resol * (float)positionY;
                    while (positionX < resultImage.Width)             //From left to right
                    {   //X coordinate
                        coordX = resol * (float)positionX;
                        setColorMap(positionX, positionY);   // collect positions of same color
                        pixelProcessed++;
                        positionX++;
                    }
                    positionX--; positionY--; coordY = resol * (float)positionY;
                    while ((positionX >= 0) & (positionY >= 0))         //From right to left
                    {   //X coordinate
                        coordX = resol * (float)positionX;
                        setColorMap(positionX, positionY);   // collect positions of same color
                        pixelProcessed++;
                        positionX--;
                    }
                    positionX++; positionY--;
                    percentDone = (pixelProcessed * 100) / pixelCount;
                    lblStatus.Text = "Generating GCode... " + Convert.ToString(percentDone) + "%";
                    if ((percentDone % 10) == 0)
                        Refresh();
                }
                if ((myToolNumber >= 0) && (colorMap.ContainsKey(myToolNumber)))
                    colorMap[myToolNumber][0].Add(colorEnd);

            #endregion

            imagegcode = "( Generated by GRBL-Plotter )\r\n";

   //         if (rBImportSVGTool.Checked)
   //             toolTable.sortByToolNr(false);
   //         else
                toolTable.sortByPixelCount(false);    // sort by color area (max. first)

            gcode.reduceGCode = true;
            convertColorMap(resol);             // generate GCode from coleccted pixel positions
            gcode.PenUp(finalString);                             // pen up
            if (!gcodeSpindleToggle) gcode.SpindleOff(finalString, "Stop spindle");
            imagegcode += gcode.GetHeader("Image import") + finalString.Replace(',', '.').ToString() + gcode.GetFooter();

            lblStatus.Text = "Done (" + Convert.ToString(pixelProcessed) + "/" + Convert.ToString(pixelCount) + ")";
            Refresh();
        }

        private static Dictionary<int, List<int>[]> colorMap = new Dictionary<int, List<int>[]>();
        private static int colorStart = -2, colorEnd = -2, lastTool = -2, lastLine = -2;
        int myToolNumber = 0;
        /// <summary>
        /// colorMap stores x-start and x-stop values for each line(y) in an array (color)
        /// </summary>
        private void setColorMap(int tmpX, int tmpY)
        {
            myToolNumber = resultToolNrArray[tmpX, (resultImage.Height -1)- tmpY];

            if ((myToolNumber >= 0) && (!colorMap.ContainsKey(myToolNumber)))   // if ToolNr unknown, create new buffer
            {   colorMap.Add(myToolNumber, new List<int>[resultImage.Height]);// add array with lines
                for (int i = 0; i < resultImage.Height; i++)
                { colorMap[myToolNumber][i] = new List<int>(); }              // add list to each line
            }

            if (lastTool != myToolNumber)                   // if new color is in use
            {   if (lastTool >= 0)
                    if (lastLine == tmpY)
                        colorMap[lastTool][tmpY].Add(colorEnd); // finish old color in same line
                    else if (lastLine >= 0)
                        colorMap[lastTool][lastLine].Add(colorEnd); // finish old color in last line
                colorStart = tmpX;
                colorEnd = tmpX;
                if (myToolNumber >= 0)                              // if regular toolNr
                    colorMap[myToolNumber][tmpY].Add(colorStart);   // start new color
            }
            else
            {   if ((lastLine != tmpY) && (lastLine >= 0) && (myToolNumber >= 0)) // still same color but line change
                { colorMap[myToolNumber][lastLine].Add(colorEnd); // finish old line
                    colorStart = tmpX;
                    colorMap[myToolNumber][tmpY].Add(colorStart);   // start new line
                }
                colorEnd = tmpX;    // still same line and same tool, just update end-pos
            }
            lastTool = myToolNumber;
            lastLine = tmpY;
        }

        /// <summary>
        /// process color map and generate gcode
        /// </summary>
        private void convertColorMap(float resol)
        {
            int toolNr, skipTooNr = 1;
            sbyte key;
            List<List<PointF>> outlineList;// = new List<List<Point>>();
            PointF tmpP = new PointF();
            float resoOutline = (float)resoDesired;
            string tmp = "";
            int pathCount =0;
            
            for (int index = 0; index < maxToolTableIndex; index++)  // go through lists
            {
                toolTable.setIndex(index);                      // set index in class
                key = (sbyte)toolTable.indexToolNr();           // if tool-nr == known key go on
                if (colorMap.ContainsKey(key))
                {   toolNr = key;                               // use tool in palette order
                    if (cbSkipToolOrder.Checked)
                        toolNr = skipTooNr++;                   // or start tool-nr at 1

                    finalString.AppendLine("\r\n( +++++ Tool change +++++ )");
                    gcode.Tool(finalString, toolNr, toolTable.indexName());  // + svgPalette.pixelCount());
                    gcode.Comment(finalString, string.Format("{0} {1} color='{2}'>", xmlMarker.figureStart, (++pathCount), toolTable.indexName()));
                    gcode.reduceGCode = false;
                    gcode.PenUp(finalString," start ");
//                    gcode.MoveToRapid(finalString, 0, 0);          // move to start pos
                    gcode.reduceGCode = true;

                    tmp += toolTable.indexName() + "\r\n";

                    if (cBGCodeOutline.Checked)
                    {
                        int smoothCnt = (int)nUDGCodeOutlineSmooth.Value;
                        if (!cBGCodeOutlineSmooth.Checked)
                            smoothCnt = 0;
                        outlineList = Vectorize.getPaths(resultToolNrArray, adjustedImage.Width, adjustedImage.Height, key, smoothCnt, (float)0.5/resoOutline, cBGCodeOutlineShrink.Checked);// half pen-width in pixels
                        int cnt = 0;
                        float tmpY;
                        foreach (List<PointF> path in outlineList)
                        {
                            if (path.Count > 0)
                            {   cnt++;
                                tmpP = path[0];
                                tmpY = (adjustedImage.Height - 1) - tmpP.Y; // start point
                                gcode.Comment(finalString, string.Format("{0} {1}>", xmlMarker.contourStart, cnt));
                                gcode.MoveToRapid(finalString, tmpP.X * resoOutline, tmpY * resoOutline, "");          // move to start pos
                                gcode.PenDown(finalString);// " contour "+cnt);
                                foreach (PointF aP in path)
                                {
                                    tmpY = (adjustedImage.Height - 1) - aP.Y;
                                    gcode.MoveTo(finalString, aP.X * resoOutline, tmpY * resoOutline);
                                }
                                tmpY = (adjustedImage.Height - 1) - tmpP.Y;
                                gcode.PenUp(finalString,"");
                                gcode.Comment(finalString, string.Format("{0}>", xmlMarker.contourEnd));
                            }
                        }
                        shrink = 0.4f;// 0.8f;          // shrink value in pixels!
                        if(cBGCodeOutlineShrink.Checked)
                            shrink = resoOutline*resoFactor*1.2f;   // mm/px * factor * 1,6
                        tmp += "\r\nTool Nr "+ key + " Points: "+cnt+" \r\n"+Vectorize.logList.ToString();
                    }
                    else
                        shrink = 0;

                    if (cBGCodeFill.Checked)
                    {   int factor = resoFactor;
                        int start = factor / 2;
                        // if (cBGCodeOutlineShrink.Checked)
                        //     start = factor; 
                        // if shrink, adapt start and stop posditions
                        gcode.Comment(finalString, string.Format("{0}>", xmlMarker.fillStart));
                        if (shrink > 0)
                        {
                            int pos1, pos2, min,max,minO,maxO, center;
                            int pxOffset = (int)(shrink / (float)resoDesired);
                            for (int y = start; y < resultImage.Height; y += factor)  // go through all lines
                            {   if (colorMap[key][y].Count > 1)
                                {   for (int k = 0; k < colorMap[key][y].Count; k += 2)
                                    {   pos1 = colorMap[key][y][k];
                                        pos2 = colorMap[key][y][k+1];
                                        min = Math.Min(pos1, pos2);
                                        max = Math.Max(pos1, pos2);
                                        minO = min + pxOffset;
                                        maxO = max - pxOffset;
                                        center = (min + max) / 2;
                                        if (minO < maxO)
                                        {   colorMap[key][y][k] = minO;
                                            colorMap[key][y][k+1] = maxO;
                                        }
                                        else if ((minO > max) || (maxO < min))
                                        {   colorMap[key][y][k] =-1;
                                            colorMap[key][y][k + 1] = -1;
                                        }
                                        else
                                        {   colorMap[key][y][k] = center;
                                            colorMap[key][y][k + 1] = center;
                                        }
                                    }
                                }
                            }
                        }

                        for (int y=start; y < resultImage.Height; y+= factor)  // go through all lines
                        {   while (colorMap[key][y].Count > 1)          // start at line 0 and check line by line
                                drawColorMap(resol, key, y, 0, true);
                        }
                        gcode.Comment(finalString, string.Format("{0}>", xmlMarker.fillEnd));
                    }
                    gcode.Comment(finalString, string.Format("{0}>", xmlMarker.figureEnd));
                }
            }
//            System.Windows.Forms.Clipboard.SetText(tmp);
        }

        private static float shrink = 0.5f;
        /// <summary>
        /// check recursive line by line for same color near by, by given x-value
        /// </summary>
        private void drawColorMap(float reso, int toolNr, int line, int startIndex, bool first)
        {
            int start, stop, newIndex;
            float coordY = reso * (float)line;
            int factor = resoFactor;

            if (colorMap[toolNr][line].Count > 1)   // at least two numbers needed: start, stop
            {
                if ((startIndex % 2 == 0) && ((startIndex + 1) < colorMap[toolNr][line].Count)) // get start,stop pair
                {   // if startIndex is even, then start = start of gcode
                    start = colorMap[toolNr][line][startIndex];     // inital start of color
                    stop = colorMap[toolNr][line][startIndex + 1];  // inital stop of color
                    colorMap[toolNr][line].RemoveRange(startIndex, 2);  // remove pair from list - needs to be drawn just once
                }
                else
                {   // if its odd, get stop-pos first, but this is start for gcode
                    start = colorMap[toolNr][line][startIndex];     // inital stop of color
                    stop = colorMap[toolNr][line][startIndex - 1];    // inital start of color
                    colorMap[toolNr][line].RemoveRange(startIndex - 1, 2);  // remove pair from list - needs to be drawn just once
                }
                float tmpShrink = shrink;
                if (start > stop) tmpShrink = -shrink;
                tmpShrink = 0;
                if ((start >= 0) && (stop >= 0) && (cBGCodeFill.Checked))
                {   float coordX = reso * (float)start + tmpShrink;
                    if (first)                                              // is this inital first call?
                    {   gcode.MoveToRapid(finalString, coordX, coordY, "1st pos.");     // move to start pos
                        gcode.PenDown(finalString);
                        first = false;
                    }
                    else
                        gcode.MoveTo(finalString, coordX, coordY);          // move to start pos
                    coordX = reso * (float)stop - tmpShrink;
                    gcode.MoveTo(finalString, coordX, coordY);              // move to end pos
                }
                if (line < (resultImage.Height - factor -1))
                {
                    var nextLine = colorMap[toolNr][line + factor];      // check for start-pos nearby in next line
                    bool end = true;
                    int dir = Math.Sign(tmpShrink);
                    int sfactor = (int)(factor * 1.5);

                    if (start < stop)                                   // comming from left, search from rigth to left
                    {   for (int k = stop + sfactor; k >= start - sfactor; k--)
                        {   newIndex = nextLine.IndexOf(k);             // first check direction were I came from
                            if (newIndex >= 0)                          // entry found
                            {   drawColorMap(reso, toolNr, line + factor, newIndex, first);  // go on with next line
                                end = false;
                                break;
                            }
                        }
                    } else
                    {                                                   // comming from right, search from left to right
                        for (int k = stop - sfactor; k <= start + sfactor; k++)
                        {   newIndex = nextLine.IndexOf(k);             // first check direction were I came from
                            if (newIndex >= 0)                          // entry found
                            {   drawColorMap(reso, toolNr, line + factor, newIndex, first);  // go on with next line
                                end = false;
                                break;
                            }
                        }
                    }

                    if (end)
                    { gcode.PenUp(finalString, " end1 "); }
                }
                else
                    gcode.PenUp(finalString, " end2 ");
            }
        }


        private void generateGCodeDiagonal()
        {
            Logger.Debug("generateGCodeDiagonal");
            if (resultImage == null) return;            //if no image, do nothing

            float resol = (float)nUDReso.Value;
            int pixelCount = resultImage.Width * resultImage.Height;
            int pixelProcessed = 0;
            int percentDone = 0;
            generateGCodePreset(0, 0);                   // 1st position

            //////////////////////////////////////////////
            // Generate Gcode lines by Diagonal scanning
            //////////////////////////////////////////////
            #region diagonal and  drawPixel
            //Start image
            int positionX = 0;
            int positionY = 0;
            lastX = 0;//reset last positions
            lastY = 0;
            int edge, direction;
            gcode.reduceGCode = true;

            while ((positionX < resultImage.Width) | (positionY < resultImage.Height))
            {
                edge = 1;
                direction = 2;    // up-left to low-right
                while ((positionX < resultImage.Width) & (positionY >= 0))
                {
                    coordY = resol * (float)positionY;
                    coordX = resol * (float)positionX;

                    drawPixel(positionX, positionY, coordX, coordY, edge, direction);
                    edge = 0;
                    pixelProcessed++;
                    positionX++;
                    positionY--;
                    if ((positionX >= resultImage.Width - 1) || (positionY <= 0)) edge = 2;  //&& (lin == 0)
                }
                positionX--;
                positionY++;

                if (positionX >= resultImage.Width - 1) positionY++;
                else positionX++;
                edge = 1;
                direction = -2;    // low-right to up-left 
                while ((positionX >= 0) & (positionY < resultImage.Height))
                {
                    coordY = resol * (float)positionY;
                    coordX = resol * (float)positionX;

                    drawPixel(positionX, positionY, coordX, coordY, edge, direction);
                    edge = 0;
                    pixelProcessed++;
                    positionX--;
                    positionY++;
                    if ((positionX <= 0) || (positionY >= resultImage.Height - 1)) edge = 2;  //&& (lin >= adjustedImage.Height-1)
                }
                positionX++;
                positionY--;
                if (positionY >= resultImage.Height - 1) positionX++;
                else positionY++;
                percentDone = (pixelProcessed * 100) / pixelCount;
                lblStatus.Text = "Generating GCode... " + Convert.ToString(percentDone) + "%";
                if ((percentDone % 10) == 0)
                    Refresh();
            }
            #endregion

            imagegcode = "( Generated by GRBL-Plotter )\r\n";

            int key;
            int skipTooNr = 0;
            int toolNr = 0;

 //           if (rBImportSVGTool.Checked)
 //               toolTable.sortByToolNr(false);
 //           else
                toolTable.sortByPixelCount(false);    // sort by color area (max. first)

            int pathCount=0;

            gcode.reduceGCode = false;
            for (int index = 0; index < maxToolTableIndex; index++)
            {
                toolTable.setIndex(index);
                key = toolTable.indexToolNr();
                if (gcodeByToolNr.ContainsKey(key))
                {
                    toolNr = key;
                    if (cbSkipToolOrder.Checked)
                        toolNr = skipTooNr++;
                    finalString.AppendLine("\r\n( +++++ Tool change +++++ )");
                    gcode.Tool(finalString, toolNr, toolTable.indexName());  // + svgPalette.pixelCount());

                    gcode.Comment(finalString, string.Format("{0} {1} color='{2}'>", xmlMarker.figureStart, (++pathCount), toolTable.indexName()));
                    gcode.PenUp(finalString);                             // pen up
                    finalString.Append(gcodeByToolNr[key]);
                    gcode.Comment(finalString, string.Format("{0}>", xmlMarker.figureEnd));
                }
            }
            gcode.PenUp(finalString);                             // pen up
            if (!gcodeSpindleToggle) gcode.SpindleOff(finalString, "Stop spindle");
            imagegcode += gcode.GetHeader("Image import") + finalString.Replace(',', '.').ToString() + gcode.GetFooter();

            lblStatus.Text = "Done (" + Convert.ToString(pixelProcessed) + "/" + Convert.ToString(pixelCount) + ")";
            Refresh();
        }

        private float lastDrawX, lastDrawY;
        bool lastIfBackground = false;
        private void drawPixel(int picPosX, int picPosY, float coordX, float coordY, int edge, int dir)
        {   int myToolNumber = resultToolNrArray[picPosX, (resultImage.Height - 1) - picPosY];
            bool ifBackground = false;
            float myX = coordX;
            float myY = coordY;
            ifBackground = (myToolNumber < 0) ? true : false;
            if (edge == 0)
            {
                if (dir == -1) myX += (float)nUDReso.Value;
                if (dir == -2) myX += (float)nUDReso.Value;
                if (dir == 2) myY += (float)nUDReso.Value;
            }
            if ((lastTool != myToolNumber) || (edge > 0))
            {
                if ((edge != 1) && !lastIfBackground)
                { lineEnd(myX, myY); }
                if (myToolNumber >= 0) gcodeStringIndex = myToolNumber;
                if ((edge != 2) && !ifBackground)
                { lineStart(myX, myY); }
                lastDrawX = coordX;
                lastDrawY = coordY;
            }

            lastTool = myToolNumber;
            lastX = coordX; lastY = coordY;
            lastIfBackground = ifBackground;
        }

        private void lineEnd(float x, float y, string txt = "")   // finish line with old pen
        {   gcode.MoveTo(tmpString, x, y, txt);          // move to end pos
            gcode.PenUp(tmpString);                             // pen up
            if ((gcodeStringIndex >= 0) && (!gcodeByToolNr.ContainsKey(gcodeStringIndex)))   // if ToolNr unknown, create new buffer
            {   gcodeByToolNr.Add(gcodeStringIndex, new StringBuilder());// add array with lines
            }
            gcodeByToolNr[gcodeStringIndex].Append(tmpString);
            tmpString.Clear();
        }
        private void lineStart(float x, float y, string txt = "")
        {
  //          gcode.reduceGCode = false;
            gcode.MoveToRapid(tmpString, x, y, txt);         // rapid move to start pos
            gcode.PenDown(tmpString);                           // pen down
  //          gcode.reduceGCode = true;
        }

        private void generateHeightData()
        {
            Logger.Debug("generateHeightData");
            if (resultImage == null) return;                //if no image, do nothing

            resultImage = new Bitmap(adjustedImage);
            float resol = (float)nUDReso.Value;
            int pixelCount = resultImage.Width * resultImage.Height;
            int pixelProcessed = 0;
            int percentDone = 0;

            int positionY;//top/botom pixel
            int positionX;//Left/right pixel
            bool useZ = rBGrayZ.Checked;

            generateGCodePreset(0, resol*(resultImage.Height - 1)); // 1st position
            gcode.Comment(finalString, string.Format("{0} {1}>", xmlMarker.figureStart, 1));

            if (rbEngravingPattern1.Checked)        // horizontal
            {
                //Start image
                positionY = resultImage.Height - 1;//top tile
                positionX = 0;//Left pixel
                coordX = resol * (float)positionX;
                coordY = resol * (float)positionY;
                lastX = coordX; lastY = coordY;
                while (positionY >= 0)
                {
                    coordY = resol * (float)positionY;
                    while (positionX < resultImage.Width)   //From left to right
                    {
                        coordX = resol * (float)positionX;
                        drawHeight(positionX, positionY, coordX, coordY, useZ);
                        pixelProcessed++; positionX++;
                    }
                    positionX--; positionY--; coordY = resol * (float)positionY;

                    while ((positionX >= 0) & (positionY >= 0))     //From right to left
                    {
                        coordX = resol * (float)positionX;
                        drawHeight(positionX, positionY, coordX, coordY, useZ);
                        pixelProcessed++; positionX--;
                    }
                    positionX++; positionY--;
                    percentDone = (pixelProcessed * 100) / pixelCount;
                    lblStatus.Text = "Generating GCode... " + Convert.ToString(percentDone) + "%";
                    if ((percentDone % 10) == 0)
                        Refresh();
                }
            }
            else
            {
                //Start image
                positionX = 0;
                positionY = 0;
                lastX = 0;//reset last positions
                lastY = 0;
                while ((positionX < resultImage.Width) | (positionY < resultImage.Height))
                {
                    while ((positionX < resultImage.Width) & (positionY >= 0))
                    {
                        coordY = resol * (float)positionY;
                        coordX = resol * (float)positionX;
                        drawHeight(positionX, positionY, coordX, coordY, useZ);
                        pixelProcessed++; positionX++; positionY--;
                    }
                    positionX--; positionY++;

                    if (positionX >= resultImage.Width - 1) positionY++;
                    else positionX++;

                    while ((positionX >= 0) & (positionY < resultImage.Height))
                    {
                        coordY = resol * (float)positionY;
                        coordX = resol * (float)positionX;
                        drawHeight(positionX, positionY, coordX, coordY, useZ);
                        pixelProcessed++; positionX--; positionY++;
                    }
                    positionX++; positionY--;
                    if (positionY >= resultImage.Height - 1) positionX++;
                    else positionY++;
                    percentDone = (pixelProcessed * 100) / pixelCount;
                    lblStatus.Text = "Generating GCode... " + Convert.ToString(percentDone) + "%";
                    if ((percentDone % 10) == 0)
                        Refresh();
                }
            }
            /*        if (rBProcessZ.Checked && rBGrayS.Checked)
                        finalString.AppendFormat("M5");
                    else
                        gcode.PenUp(finalString);                             // pen up
                        */
            gcode.Comment(finalString, string.Format("{0} 1>", xmlMarker.figureEnd));
            gcode.jobEnd(finalString);

            //       if (!gcodeSpindleToggle) gcode.SpindleOff(finalString, "Stop spindle");

            imagegcode = "( Generated by GRBL-Plotter )\r\n";
            imagegcode += gcode.GetHeader("Image import") + finalString.Replace(',', '.').ToString() + gcode.GetFooter();
        }

        private void drawHeight(int col, int lin, float coordX, float coordY, bool useZ)
        {
            Color myColor = resultImage.GetPixel(col, (resultImage.Height - 1) - lin);          // Get pixel color
            double height = 255 - Math.Round((double)(myColor.R + myColor.G + myColor.B) / 3);  // calc height
            if (useZ)
            {
                float coordZ = (float)((double)nUDZTop.Value - height * (double)(nUDZTop.Value - nUDZBot.Value) / 255);    // calc Z value
                string feed = string.Format("F{0}", gcode.gcodeXYFeed);
                gcode.MoveTo(finalString, coordX, coordY, coordZ, "");
            }
            else
            {
                float power = (float)((double)nUDSMin.Value - height * (double)(nUDSMin.Value - nUDSMax.Value) / 255);    // calc Z value
                finalString.AppendFormat("G{0} X{1} Y{2} S{3}\r\n", gcode.frmtCode(1),gcode.frmtNum(coordX), gcode.frmtNum(coordY), Math.Round(power));
            }
        }

    }
}
